package org.eraser.aspect;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.util.concurrent.RateLimiter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.eraser.common.SystemException;
import org.eraser.common.SystemStatusCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * google 限流算法 实现注解限流
 */
@Slf4j
@Aspect
@Component
public class GuavaLimiterAspect {

    private final double default_limiter_count_per_second = 0.5;

    // 缓存中的条目在最后一次访问后10分钟内过期
    Cache<String, RateLimiter> limiterCache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build();

    /**
     * 此处的 HttpServletRequest 是否线程安全呢 <a href="https://blog.csdn.net/guo_xl/article/details/108006047">...</a>
     */
    @Autowired
    private HttpServletRequest request;

    public GuavaLimiterAspect(HttpServletRequest request) {
        this.request = request;
    }

    @Pointcut("@annotation(org.eraser.aspect.LimiterAspect)")
    public void pointcut() {}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // nginx 透传过来的 真实用户IP
        String ip = request.getHeader("X-Real-IP");
        Signature signature = proceedingJoinPoint.getSignature();

        MethodSignature methodSignature = (MethodSignature) signature;
        String methodName = proceedingJoinPoint.getTarget().getClass().getName() + "." + methodSignature.getName();
        String recordKey = ip + ">>>" + methodName;

        // RateLimiter.create(default_limiter_count_per_second) 每秒生成0.5个令牌。 也就是每2秒只能请求一次。
        RateLimiter rateLimiter = limiterCache.get(recordKey, () -> RateLimiter.create(default_limiter_count_per_second));
        if (!rateLimiter.tryAcquire()) {
            log.error("IP >>> METHOD {}. Too many requests", recordKey);
            throw new SystemException(SystemStatusCode.S500.getCode(), "Too many requests");
        }
        return proceedingJoinPoint.proceed();
    }


}
